package org.pirx.utils;

import org.pirx.annotations.mutator;
import org.pirx.annotations.observer;
import org.pirx.defaults.ParametersGenerator;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Iterator;

/**
 * Created by IntelliJ IDEA.
 * User: tonyx
 * Date: Apr 20, 2008
 * Time: 5:08:30 PM
 * To change this template use File | Settings | File Templates.
 */
public class Utilities {
    public static boolean strictlyEquivalents(Class contractClass, Object first, Object second) throws IllegalAccessException, InvocationTargetException {

          if (first == null && second == null)
              return true;
          if (first == null || second == null)
              return false;

          // has sense if the eventually redefined equals in a way coherent with
          // a type herarchy
          // deprecated: we don't want relie on already existing definition
          /*
          if (Utils.redefinesEquals(first)&&Utils.redefinesEquals(second))
          {
              return first.equals(second);
          }
          */

          Class firstInterfaces[] = first.getClass().getInterfaces();
          Class secondInterfaces[] = second.getClass().getInterfaces();

          Set firstHashSet = new HashSet();
          Set secondHashSet = new HashSet();

          for (int i = 0; i < firstInterfaces.length; i++) {
              firstHashSet.add(firstInterfaces[i]);
          }
          for (int i = 0; i < secondInterfaces.length; i++) {
              secondHashSet.add(secondInterfaces[i]);
          }


          if (!(isAncestor(first, contractClass) || firstHashSet.contains(contractClass))) {
              return false;
          }
          if (!(isAncestor(second, contractClass) || secondHashSet.contains(contractClass))) {
              return false;
          }

          // get all the observers of the contractclass and check if the returned objects
          // are strictly equivalents

          HashSet observers = getObservers(contractClass);
          // collect observer values

          HashMap observerAndValueOfFirst = new HashMap();
          HashMap observerAndValueOfSecond = new HashMap();

          Iterator iterator = observers.iterator();

          while (iterator.hasNext()) {
              Method temp = (Method) iterator.next();
              observerAndValueOfFirst.put(temp, temp.invoke(first, new Object[]{}));
              observerAndValueOfSecond.put(temp, temp.invoke(second, new Object[]{}));
          }

          boolean flag = true;
          iterator = observers.iterator();
          if (iterator.hasNext()) {
              while (iterator.hasNext()) {
                  Method method = (Method) iterator.next();
                  flag = flag && (observerAndValueOfFirst.get(method)!=null?strictlyEquivalents(observerAndValueOfFirst.get(method).getClass(), observerAndValueOfFirst.get(method), observerAndValueOfSecond.get(method)):observerAndValueOfSecond.get(method)==null);
              }
          } else {
              flag = (first.equals(second));
          }
          return flag;
      }



      public static boolean behaviourallyEquivalents(Class contractClass, Object first, Object second,int seed, int iterations) throws IllegalAccessException, InvocationTargetException, InstantiationException {

           if (!strictlyEquivalents(contractClass,first,second))
           {
               return false;
           }

           Class firstInterfaces[] = first.getClass().getInterfaces();
           Class secondInterfaces[] = second.getClass().getInterfaces();

           Set firstHashSet = new HashSet<Class>();
           Set secondHashSet = new HashSet<Class>();

           for (int i = 0; i < firstInterfaces.length; i++) {
               firstHashSet.add(firstInterfaces[i]);
           }

           for (int i = 0; i < secondInterfaces.length; i++) {
               secondHashSet.add(secondInterfaces[i]);
           }

           if (!(isAncestor(first, contractClass) || firstHashSet.contains(contractClass))) {
               return false;
           }

           if (!(isAncestor(second, contractClass) || secondHashSet.contains(contractClass))) {
               return false;
           }

           // initialize method mutators parameters generator with seed
           HashSet mutators =  getMutators(contractClass);
           HashSet observers = getObservers(contractClass);
           Iterator mutatorsIterator = mutators.iterator();

           HashMap instGenForFirst = new HashMap();
           HashMap instGenForSecond = new HashMap();

           while (mutatorsIterator.hasNext())
           {
               Method method = ((Method)mutatorsIterator.next());
               ParametersGenerator firstInstGen = (ParametersGenerator)(method.getAnnotation(mutator.class).parametersGenerator().newInstance());
               ParametersGenerator secondInstGen = (ParametersGenerator)(method.getAnnotation(mutator.class).parametersGenerator().newInstance());

               firstInstGen.reset(seed);
               secondInstGen.reset(seed);

               instGenForFirst.put(method,firstInstGen);
               instGenForSecond.put(method,secondInstGen);
           }


           boolean flag = true;
           // invoke sequencially: however there could be a calling strategy somewhere
           for (int i=0;i<iterations;i++)
           {
               mutatorsIterator=mutators.iterator();
               while (mutatorsIterator.hasNext()) {
                   Method actualMethod = (Method)mutatorsIterator.next();
                   actualMethod.invoke(first,((ParametersGenerator)(instGenForFirst.get(actualMethod))).generateAnInstancePar());
                   actualMethod.invoke(second,((ParametersGenerator)(instGenForSecond.get(actualMethod))).generateAnInstancePar());

                   flag = flag && (strictlyEquivalents(contractClass,first,second));

                   if (!flag)
                   {
                       System.out.println("unequal object state for objects "+first.toString()+" and  "+second.toString()+ " at iteration "+i);
                   }
               }
           }

           return flag;
       }



      public static HashSet<Method> getMutators(Class aClass)
      {
          HashSet<Method> hashSet = new HashSet();
          Method[] methods = aClass.getMethods();
          for (int i=0;i<methods.length;i++)
          {
              if (methods[i].getAnnotation(mutator.class)!=null)
              {
                  hashSet.add(methods[i]);
              }

          }
          return hashSet;
      }


      public static HashSet<Method> getObservers(Class aClass) {
          HashSet<Method> hashSet = new HashSet();
          Method[] methods = aClass.getMethods();
          for (int i = 0; i < methods.length; i++) {
              boolean flag = false;
              if (methods[i].getAnnotation(observer.class) != null) {
                  hashSet.add(methods[i]);
              }

          }
          return hashSet;
      }


      public static boolean isAncestor(Object first, Class aClass) {
          Class iter = first.getClass();
          while (!iter.equals(Object.class)) {
              if (iter.equals(aClass))
                  return true;

              iter = iter.getSuperclass();
          }
          return false;
      }

}
